package com.spica.platform.uaa.server;

import com.spica.platform.uaa.client.exception.DefaultAuthenticationEntryPoint;
import com.spica.platform.uaa.server.cas.CasProperties;
import com.spica.platform.uaa.server.service.impl.RedisClientDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.TokenGranter;
import org.springframework.security.oauth2.provider.code.RandomValueAuthorizationCodeServices;
import org.springframework.security.oauth2.provider.error.WebResponseExceptionTranslator;
import org.springframework.security.oauth2.provider.token.TokenStore;


/**
 * OAuth2 授权服务器配置
 */
@Configuration
@EnableAuthorizationServer
@EnableConfigurationProperties(CasProperties.class)
@AutoConfigureAfter(AuthorizationServerEndpointsConfigurer.class)
public class UAAServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 加载客户端信息用的服务类
     */
    @Autowired
    private RedisClientDetailsService clientDetailsService;

    /**
     * 授权码的存储服务类
     */
    @Autowired
    private RandomValueAuthorizationCodeServices authorizationCodeServices;

    /**
     * 注入authenticationManager 来支持 password grant type
     */
    @Autowired
    private AuthenticationManager authenticationManager;

    /**
     * 认证异常处理类
     */
    @Autowired
    private WebResponseExceptionTranslator webResponseExceptionTranslator;

    /**
     * token存放位置
     */
    @Autowired
    private TokenStore tokenStore;

    /**
     * 自定义认证方式
     */
    @Autowired
    private TokenGranter tokenGranter;

    /**
     * 配置身份认证器，配置认证方式，TokenStore，TokenGranter，OAuth2RequestFactory
     * @param endpoints
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) {
        //tokenStore存放的位置,如果是JWT,由于JWT是自包含的(自我生成的),所以不需要存放
        endpoints.tokenStore(tokenStore)
                //认证管理类
                .authenticationManager(authenticationManager)
                //授权码的存储服务类
                .authorizationCodeServices(authorizationCodeServices)
                //认证异常处理类, 处理 ExceptionTranslationFilter 抛出的异常
                .exceptionTranslator(webResponseExceptionTranslator)
                //设置TokenGranter, 主要是为了支持自定义的授权方式,配置见TokenGranterConfig
                .tokenGranter(tokenGranter);
    }

    /**
     * 配置客户端的详细信息,需要提前在这里配置好客户端信息,这就类似于GitHub第三方登录时,我们需要提前在GitHub上注册我们的应用信息.
     * 这里就是把这些客户端信息配置好,用于客户端认证
     * 客户端信息可以存放在数据库中,也可以存放在内存中
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        //客户端信息存放在由RedisClientDetailsService实现的Redis中
        clients.withClientDetails(clientDetailsService);
        //配置完了后,预加载一次(即把数据库中的客户端信息加载到Redis中)
        clientDetailsService.loadAllClientToCache();
    }

    /**
     * 对应于配置AuthorizationServer安全认证的相关信息，创建ClientCredentialsTokenEndpointFilter核心过滤器
     * @param security
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) {
        security
                .tokenKeyAccess("isAuthenticated()")
                //这里设置了checkTokenAccess端点可以自由访问,该端点的作用是当资源服务器收到AccessToken之后,需要去授权服务器校验
                //AccessToken的合法性,就会访问这个端点
                .checkTokenAccess("permitAll()")
                //让/oauth/token支持client_id以及client_secret作登录认证
                .allowFormAuthenticationForClients();
        security.authenticationEntryPoint(new DefaultAuthenticationEntryPoint());
    }

}
